home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / inkscape / extensions / interp.py < prev    next >
Encoding:
Python Source  |  2010-03-12  |  13.3 KB  |  317 lines

  1. #!/usr/bin/env python 
  2. '''
  3. Copyright (C) 2005 Aaron Spike, aaron@ekips.org
  4.  
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2 of the License, or
  8. (at your option) any later version.
  9.  
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. GNU General Public License for more details.
  14.  
  15. You should have received a copy of the GNU General Public License
  16. along with this program; if not, write to the Free Software
  17. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  18. '''
  19. import inkex, cubicsuperpath, simplestyle, copy, math, bezmisc
  20.  
  21. def numsegs(csp):
  22.     return sum([len(p)-1 for p in csp])
  23. def interpcoord(v1,v2,p):
  24.     return v1+((v2-v1)*p)
  25. def interppoints(p1,p2,p):
  26.     return [interpcoord(p1[0],p2[0],p),interpcoord(p1[1],p2[1],p)]
  27. def pointdistance((x1,y1),(x2,y2)):
  28.     return math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2))
  29. def bezlenapprx(sp1, sp2):
  30.     return pointdistance(sp1[1], sp1[2]) + pointdistance(sp1[2], sp2[0]) + pointdistance(sp2[0], sp2[1])
  31. def tpoint((x1,y1), (x2,y2), t = 0.5):
  32.     return [x1+t*(x2-x1),y1+t*(y2-y1)]
  33. def cspbezsplit(sp1, sp2, t = 0.5):
  34.     m1=tpoint(sp1[1],sp1[2],t)
  35.     m2=tpoint(sp1[2],sp2[0],t)
  36.     m3=tpoint(sp2[0],sp2[1],t)
  37.     m4=tpoint(m1,m2,t)
  38.     m5=tpoint(m2,m3,t)
  39.     m=tpoint(m4,m5,t)
  40.     return [[sp1[0][:],sp1[1][:],m1], [m4,m,m5], [m3,sp2[1][:],sp2[2][:]]]
  41. def cspbezsplitatlength(sp1, sp2, l = 0.5, tolerance = 0.001):
  42.     bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
  43.     t = bezmisc.beziertatlength(bez, l, tolerance)
  44.     return cspbezsplit(sp1, sp2, t)
  45. def cspseglength(sp1,sp2, tolerance = 0.001):
  46.     bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
  47.     return bezmisc.bezierlength(bez, tolerance)    
  48. def csplength(csp):
  49.     total = 0
  50.     lengths = []
  51.     for sp in csp:
  52.         lengths.append([])
  53.         for i in xrange(1,len(sp)):
  54.             l = cspseglength(sp[i-1],sp[i])
  55.             lengths[-1].append(l)
  56.             total += l            
  57.     return lengths, total
  58.     
  59. def tweenstylefloat(property, start, end, time):
  60.     sp = float(start[property])
  61.     ep = float(end[property])
  62.     return str(sp + (time * (ep - sp)))
  63. def tweenstyleunit(property, start, end, time):
  64.     sp = inkex.unittouu(start[property])
  65.     ep = inkex.unittouu(end[property])
  66.     return str(sp + (time * (ep - sp)))
  67. def tweenstylecolor(property, start, end, time):
  68.     sr,sg,sb = parsecolor(start[property])
  69.     er,eg,eb = parsecolor(end[property])
  70.     return '#%s%s%s' % (tweenhex(time,sr,er),tweenhex(time,sg,eg),tweenhex(time,sb,eb))
  71. def tweenhex(time,s,e):
  72.     s = float(int(s,16))
  73.     e = float(int(e,16))
  74.     retval = hex(int(math.floor(s + (time * (e - s)))))[2:]
  75.     if len(retval)==1:
  76.         retval = '0%s' % retval
  77.     return retval
  78. def parsecolor(c):
  79.     r,g,b = '0','0','0'
  80.     if c[:1]=='#':
  81.         if len(c)==4:
  82.             r,g,b = c[1:2],c[2:3],c[3:4]
  83.         elif len(c)==7:
  84.             r,g,b = c[1:3],c[3:5],c[5:7]
  85.     return r,g,b
  86.  
  87. class Interp(inkex.Effect):
  88.     def __init__(self):
  89.         inkex.Effect.__init__(self)
  90.         self.OptionParser.add_option("-e", "--exponent",
  91.                         action="store", type="float", 
  92.                         dest="exponent", default=0.0,
  93.                         help="values other than zero give non linear interpolation")
  94.         self.OptionParser.add_option("-s", "--steps",
  95.                         action="store", type="int", 
  96.                         dest="steps", default=5,
  97.                         help="number of interpolation steps")
  98.         self.OptionParser.add_option("-m", "--method",
  99.                         action="store", type="int", 
  100.                         dest="method", default=2,
  101.                         help="method of interpolation")
  102.         self.OptionParser.add_option("-d", "--dup",
  103.                         action="store", type="inkbool", 
  104.                         dest="dup", default=True,
  105.                         help="duplicate endpaths")    
  106.         self.OptionParser.add_option("--style",
  107.                         action="store", type="inkbool", 
  108.                         dest="style", default=True,
  109.                         help="try interpolation of some style properties")    
  110.     def effect(self):
  111.         exponent = self.options.exponent
  112.         if exponent>= 0:
  113.             exponent = 1.0 + exponent
  114.         else:
  115.             exponent = 1.0/(1.0 - exponent)
  116.         steps = [1.0/(self.options.steps + 1.0)]
  117.         for i in range(self.options.steps - 1):
  118.             steps.append(steps[0] + steps[-1])
  119.         steps = [step**exponent for step in steps]
  120.             
  121.         paths = {}            
  122.         styles = {}
  123.         for id in self.options.ids:
  124.             node = self.selected[id]
  125.             if node.tag ==inkex.addNS('path','svg'):
  126.                 paths[id] = cubicsuperpath.parsePath(node.get('d'))
  127.                 styles[id] = simplestyle.parseStyle(node.get('style'))
  128.             else:
  129.                 self.options.ids.remove(id)
  130.  
  131.         for i in range(1,len(self.options.ids)):
  132.             start = copy.deepcopy(paths[self.options.ids[i-1]])
  133.             end = copy.deepcopy(paths[self.options.ids[i]])
  134.             sst = copy.deepcopy(styles[self.options.ids[i-1]])
  135.             est = copy.deepcopy(styles[self.options.ids[i]])
  136.             basestyle = copy.deepcopy(sst)
  137.  
  138.             #prepare for experimental style tweening
  139.             if self.options.style:
  140.                 dostroke = True
  141.                 dofill = True
  142.                 styledefaults = {'opacity':'1.0', 'stroke-opacity':'1.0', 'fill-opacity':'1.0',
  143.                         'stroke-width':'1.0', 'stroke':'none', 'fill':'none'}
  144.                 for key in styledefaults.keys():
  145.                     sst.setdefault(key,styledefaults[key])
  146.                     est.setdefault(key,styledefaults[key])
  147.                 isnotplain = lambda x: not (x=='none' or x[:1]=='#')
  148.                 if isnotplain(sst['stroke']) or isnotplain(est['stroke']) or (sst['stroke']=='none' and est['stroke']=='none'):
  149.                     dostroke = False
  150.                 if isnotplain(sst['fill']) or isnotplain(est['fill']) or (sst['fill']=='none' and est['fill']=='none'):
  151.                     dofill = False
  152.                 if dostroke:
  153.                     if sst['stroke']=='none':
  154.                         sst['stroke-width'] = '0.0'
  155.                         sst['stroke-opacity'] = '0.0'
  156.                         sst['stroke'] = est['stroke'] 
  157.                     elif est['stroke']=='none':
  158.                         est['stroke-width'] = '0.0'
  159.                         est['stroke-opacity'] = '0.0'
  160.                         est['stroke'] = sst['stroke'] 
  161.                 if dofill:
  162.                     if sst['fill']=='none':
  163.                         sst['fill-opacity'] = '0.0'
  164.                         sst['fill'] = est['fill'] 
  165.                     elif est['fill']=='none':
  166.                         est['fill-opacity'] = '0.0'
  167.                         est['fill'] = sst['fill'] 
  168.  
  169.                     
  170.  
  171.             if self.options.method == 2:
  172.                 #subdivide both paths into segments of relatively equal lengths
  173.                 slengths, stotal = csplength(start)
  174.                 elengths, etotal = csplength(end)
  175.                 lengths = {}
  176.                 t = 0
  177.                 for sp in slengths:
  178.                     for l in sp:
  179.                         t += l / stotal
  180.                         lengths.setdefault(t,0)
  181.                         lengths[t] += 1
  182.                 t = 0
  183.                 for sp in elengths:
  184.                     for l in sp:
  185.                         t += l / etotal
  186.                         lengths.setdefault(t,0)
  187.                         lengths[t] += -1
  188.                 sadd = [k for (k,v) in lengths.iteritems() if v < 0]
  189.                 sadd.sort()
  190.                 eadd = [k for (k,v) in lengths.iteritems() if v > 0]
  191.                 eadd.sort()
  192.  
  193.                 t = 0
  194.                 s = [[]]
  195.                 for sp in slengths:
  196.                     if not start[0]:
  197.                         s.append(start.pop(0))
  198.                     s[-1].append(start[0].pop(0))
  199.                     for l in sp:
  200.                         pt = t
  201.                         t += l / stotal
  202.                         if sadd and t > sadd[0]:
  203.                             while sadd and sadd[0] < t:
  204.                                 nt = (sadd[0] - pt) / (t - pt)
  205.                                 bezes = cspbezsplitatlength(s[-1][-1][:],start[0][0][:], nt)
  206.                                 s[-1][-1:] = bezes[:2]
  207.                                 start[0][0] = bezes[2]
  208.                                 pt = sadd.pop(0)
  209.                         s[-1].append(start[0].pop(0))
  210.                 t = 0
  211.                 e = [[]]
  212.                 for sp in elengths:
  213.                     if not end[0]:
  214.                         e.append(end.pop(0))
  215.                     e[-1].append(end[0].pop(0))
  216.                     for l in sp:
  217.                         pt = t
  218.                         t += l / etotal
  219.                         if eadd and t > eadd[0]:
  220.                             while eadd and eadd[0] < t:
  221.                                 nt = (eadd[0] - pt) / (t - pt)
  222.                                 bezes = cspbezsplitatlength(e[-1][-1][:],end[0][0][:], nt)
  223.                                 e[-1][-1:] = bezes[:2]
  224.                                 end[0][0] = bezes[2]
  225.                                 pt = eadd.pop(0)
  226.                         e[-1].append(end[0].pop(0))
  227.                 start = s[:]
  228.                 end = e[:]
  229.             else:
  230.                 #which path has fewer segments?
  231.                 lengthdiff = numsegs(start) - numsegs(end)
  232.                 #swap shortest first
  233.                 if lengthdiff > 0:
  234.                     start, end = end, start
  235.                 #subdivide the shorter path
  236.                 for x in range(abs(lengthdiff)):
  237.                     maxlen = 0
  238.                     subpath = 0
  239.                     segment = 0
  240.                     for y in range(len(start)):
  241.                         for z in range(1, len(start[y])):
  242.                             leng = bezlenapprx(start[y][z-1], start[y][z])
  243.                             if leng > maxlen:
  244.                                 maxlen = leng
  245.                                 subpath = y
  246.                                 segment = z
  247.                     sp1, sp2 = start[subpath][segment - 1:segment + 1]
  248.                     start[subpath][segment - 1:segment + 1] = cspbezsplit(sp1, sp2)
  249.                 #if swapped, swap them back
  250.                 if lengthdiff > 0:
  251.                     start, end = end, start
  252.             
  253.             #break paths so that corresponding subpaths have an equal number of segments
  254.             s = [[]]
  255.             e = [[]]
  256.             while start and end:
  257.                 if start[0] and end[0]:
  258.                     s[-1].append(start[0].pop(0))
  259.                     e[-1].append(end[0].pop(0))
  260.                 elif end[0]:
  261.                     s.append(start.pop(0))
  262.                     e[-1].append(end[0][0])
  263.                     e.append([end[0].pop(0)])
  264.                 elif start[0]:
  265.                     e.append(end.pop(0))
  266.                     s[-1].append(start[0][0])
  267.                     s.append([start[0].pop(0)])
  268.                 else:
  269.                     s.append(start.pop(0))
  270.                     e.append(end.pop(0))
  271.     
  272.             if self.options.dup:
  273.                 steps = [0] + steps + [1]    
  274.             #create an interpolated path for each interval
  275.             group = inkex.etree.SubElement(self.current_layer,inkex.addNS('g','svg'))    
  276.             for time in steps:
  277.                 interp = []
  278.                 #process subpaths
  279.                 for ssp,esp in zip(s, e):
  280.                     if not (ssp or esp):
  281.                         break
  282.                     interp.append([])
  283.                     #process superpoints
  284.                     for sp,ep in zip(ssp, esp):
  285.                         if not (sp or ep):
  286.                             break
  287.                         interp[-1].append([])
  288.                         #process points
  289.                         for p1,p2 in zip(sp, ep):
  290.                             if not (sp or ep):
  291.                                 break
  292.                             interp[-1][-1].append(interppoints(p1, p2, time))
  293.  
  294.                 #remove final subpath if empty.
  295.                 if not interp[-1]:
  296.                     del interp[-1]
  297.  
  298.                 #basic style tweening
  299.                 if self.options.style:
  300.                     basestyle['opacity'] = tweenstylefloat('opacity',sst,est,time)
  301.                     if dostroke:
  302.                         basestyle['stroke-opacity'] = tweenstylefloat('stroke-opacity',sst,est,time)
  303.                         basestyle['stroke-width'] = tweenstyleunit('stroke-width',sst,est,time)
  304.                         basestyle['stroke'] = tweenstylecolor('stroke',sst,est,time)
  305.                     if dofill:
  306.                         basestyle['fill-opacity'] = tweenstylefloat('fill-opacity',sst,est,time)
  307.                         basestyle['fill'] = tweenstylecolor('fill',sst,est,time)
  308.                 attribs = {'style':simplestyle.formatStyle(basestyle),'d':cubicsuperpath.formatPath(interp)}
  309.                 new = inkex.etree.SubElement(group,inkex.addNS('path','svg'), attribs)
  310.  
  311. if __name__ == '__main__':
  312.     e = Interp()
  313.     e.affect()
  314.  
  315.  
  316. # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
  317.